/*
 * Copyright (C) 2010-2018 Gordon Fraser, Andrea Arcuri and EvoSuite
 * contributors
 *
 * This file is part of EvoSuite.
 *
 * EvoSuite is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 3.0 of the License, or
 * (at your option) any later version.
 *
 * EvoSuite is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with EvoSuite. If not, see <http://www.gnu.org/licenses/>.
 */
package org.evosuite.graphs.cfg;

import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;

import org.evosuite.graphs.GraphPool;
import org.evosuite.graphs.cdg.ControlDependenceGraph;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class is used to represent basic blocks in the control flow graph.
 * 
 * A basic block is a list of instructions for which the following holds:
 * 
 * Whenever control flow reaches the first instruction of this blocks list,
 * control flow will pass through all the instructions of this list successively
 * and not pass another instruction of the underlying method in the mean time.
 * The first element in this blocks list does not have a parent in the CFG that
 * can be prepended to the list and the same would still hold true Finally the
 * last element in this list does not have a child inside the CFG that could be
 * appended to the list such that the above still holds true
 * 
 * In other words: - the first/last element of this blocks list has either 0 or
 * >=2 parents/children in the CFG - every other element in the list has exactly
 * 1 parent and exactly 1 child in the raw CFG
 * 
 * 
 * Taken from:
 * 
 * "Efficiently Computing Static Single Assignment Form and the Control
 * Dependence Graph" RON CYTRON, JEANNE FERRANTE, BARRY K. ROSEN, and MARK N.
 * WEGMAN IBM Research Division and F. KENNETH ZADECK Brown University 1991
 * 
 * @see cfg.ActualControlFlowGraph
 * @author Andre Mis
 */
public class BasicBlock implements Serializable, Iterable<BytecodeInstruction> {

	private static final long serialVersionUID = -3465486470017841484L;

	private static final Logger logger = LoggerFactory.getLogger(BasicBlock.class);

	private static int blockCount = 0;

	private int id = -1;
	protected ClassLoader classLoader;
	protected String className;
	protected String methodName;

	// experiment: since finding the control dependent branches in the CDG might
	// take a little to long, we might want to remember them
	private Set<ControlDependency> controlDependencies;
	private Set<Integer> controlDependentBranchIDs;

	protected boolean isAuxiliaryBlock = false;

	private final List<BytecodeInstruction> instructions = new ArrayList<>();

	// DONE reference each BytecodeInstruction's BasicBlock at the instruction
	// DONE determine ControlDependentBranches once for each BasicBlock, then
	// ask BasicBloc, whenever instruction is asked
	// TODO remember distance to each control dependent Branch in order to speed
	// up ControlFlowDistance calculation even more

	/**
	 * <p>
	 * Constructor for BasicBlock.
	 * </p>
	 * 
	 * @param className
	 *            a {@link java.lang.String} object.
	 * @param methodName
	 *            a {@link java.lang.String} object.
	 * @param blockNodes
	 *            a {@link java.util.List} object.
	 */
	public BasicBlock(ClassLoader classLoader, String className, String methodName,
	        List<BytecodeInstruction> blockNodes) {
		if (className == null || methodName == null || blockNodes == null)
			throw new IllegalArgumentException("null given");

		this.className = className;
		this.methodName = methodName;
		this.classLoader = classLoader;

		setId();
		setInstructions(blockNodes);

		checkSanity();
	}

	/**
	 * Used by Entry- and ExitBlocks
	 * 
	 * @param className
	 *            a {@link java.lang.String} object.
	 * @param methodName
	 *            a {@link java.lang.String} object.
	 */
	protected BasicBlock(String className, String methodName) {
		if (className == null || methodName == null)
			throw new IllegalArgumentException("null given");

		this.className = className;
		this.methodName = methodName;
		this.isAuxiliaryBlock = true;
	}

	// CDs

	/**
	 * Returns the ControlDependenceGraph of this instructions method
	 * 
	 * Convenience method. Redirects the call to GraphPool.getCDG()
	 * 
	 * @return a {@link org.evosuite.graphs.cdg.ControlDependenceGraph} object.
	 */
	public ControlDependenceGraph getCDG() {

		ControlDependenceGraph myCDG = GraphPool.getInstance(classLoader).getCDG(className,
		                                                                         methodName);
		if (myCDG == null)
			throw new IllegalStateException(
			        "expect GraphPool to know CDG for every method for which an instruction is known");

		return myCDG;
	}

	/**
	 * Returns all branchIds of Branches this instruction is directly control
	 * dependent on as determined by the ControlDependenceGraph for this
	 * instruction's method.
	 * 
	 * If this instruction is control dependent on the root branch the id -1
	 * will be contained in this set
	 * 
	 * @return a {@link java.util.Set} object.
	 */
	public Set<Integer> getControlDependentBranchIds() {

		ControlDependenceGraph myDependence = getCDG();

		if (controlDependentBranchIDs == null) {
			controlDependentBranchIDs = myDependence.getControlDependentBranchIds(this);
			//be sure we can iterate over it deterministically
			controlDependentBranchIDs =
					controlDependentBranchIDs.stream().sorted().collect(Collectors.toCollection(LinkedHashSet::new));
		}
		return controlDependentBranchIDs;
	}

	/**
	 * Returns a cfg.Branch object for each branch this instruction is control
	 * dependent on as determined by the ControlDependenceGraph. If this
	 * instruction is only dependent on the root branch this method returns an
	 * empty set
	 * 
	 * If this instruction is a Branch and it is dependent on itself - which can
	 * happen in loops for example - the returned set WILL contain this. If you
	 * do not need the full set in order to avoid loops, call
	 * getAllControlDependentBranches instead
	 * 
	 * @return a {@link java.util.Set} object.
	 */
	public Set<ControlDependency> getControlDependencies() {

		if (controlDependencies == null)
			controlDependencies = getCDG().getControlDependentBranches(this);

		//		return new HashSet<ControlDependency>(controlDependentBranches);
		return controlDependencies;
	}

	/**
	 * <p>
	 * hasControlDependenciesSet
	 * </p>
	 * 
	 * @return a boolean.
	 */
	public boolean hasControlDependenciesSet() {
		return controlDependencies != null;
	}

	// initialization

	private void setInstructions(List<BytecodeInstruction> blockNodes) {
		for (BytecodeInstruction instruction : blockNodes) {
			if (!appendInstruction(instruction))
				throw new IllegalStateException(
				        "internal error while addind instruction to basic block list");
		}
		if (instructions.isEmpty())
			throw new IllegalStateException(
			        "expect each basic block to contain at least one instruction");
	}

	private boolean appendInstruction(BytecodeInstruction instruction) {
		if (instruction == null)
			throw new IllegalArgumentException("null given");
		if (!className.equals(instruction.getClassName()))
			throw new IllegalArgumentException(
			        "expect elements of a basic block to be inside the same class");
		if (!methodName.equals(instruction.getMethodName()))
			throw new IllegalArgumentException(
			        "expect elements of a basic block to be inside the same class");
		if (instruction.hasBasicBlockSet())
			throw new IllegalArgumentException(
			        "expect to get instruction without BasicBlock already set");
		if (instructions.contains(instruction))
			throw new IllegalArgumentException(
			        "a basic block can not contain the same element twice");

		// not sure if this holds:
		// .. apparently it doesn't. at least check
		// fails for java2.util2.Pattern TODO

		// BytecodeInstruction previousInstruction = getLastInstruction();
		// if (previousInstruction != null
		// && instruction.getInstructionId() < previousInstruction
		// .getInstructionId())
		// throw new IllegalStateException(
		// "expect instructions in a basic block to be ordered by their instructionId");

		instruction.setBasicBlock(this);

		return instructions.add(instruction);
	}

	private void setId() {
		blockCount++;
		this.id = blockCount;
	}

	// retrieve information

	/**
	 * <p>
	 * containsInstruction
	 * </p>
	 * 
	 * @param instruction
	 *            a {@link org.evosuite.graphs.cfg.BytecodeInstruction} object.
	 * @return a boolean.
	 */
	public boolean containsInstruction(BytecodeInstruction instruction) {
		if (instruction == null)
			throw new IllegalArgumentException("null given");

		return instructions.contains(instruction);
	}

	/**
	 * <p>
	 * constainsInstruction
	 * </p>
	 * 
	 * @param insnNode
	 *            a {@link org.objectweb.asm.tree.AbstractInsnNode} object.
	 * @return a boolean.
	 */
	public boolean constainsInstruction(AbstractInsnNode insnNode) {
		for (BytecodeInstruction instruction : instructions) {
			if (instruction.getASMNode().equals(insnNode))
				return true;
		}
		return false;
	}

	/**
	 * <p>
	 * getFirstInstruction
	 * </p>
	 * 
	 * @return a {@link org.evosuite.graphs.cfg.BytecodeInstruction} object.
	 */
	public BytecodeInstruction getFirstInstruction() {
		if (instructions.isEmpty())
			return null;
		return instructions.get(0);
	}

	/**
	 * <p>
	 * getLastInstruction
	 * </p>
	 * 
	 * @return a {@link org.evosuite.graphs.cfg.BytecodeInstruction} object.
	 */
	public BytecodeInstruction getLastInstruction() {
		if (instructions.isEmpty())
			return null;
		return instructions.get(instructions.size() - 1);
	}

	/**
	 * <p>
	 * getFirstLine
	 * </p>
	 * 
	 * @return a int.
	 */
	public int getFirstLine() {
		for (BytecodeInstruction ins : instructions)
			if (ins.hasLineNumberSet())
				return ins.getLineNumber();

		return -1;
	}

	/**
	 * <p>
	 * getLastLine
	 * </p>
	 * 
	 * @return a int.
	 */
	public int getLastLine() {

		int r = -1;

		for (BytecodeInstruction ins : instructions)
			if (ins.hasLineNumberSet())
				r = ins.getLineNumber();

		return r;
	}

	/**
	 * <p>
	 * getName
	 * </p>
	 * 
	 * @return a {@link java.lang.String} object.
	 */
	public String getName() {
		return (isAuxiliaryBlock ? "aux" : "") + "BasicBlock " + id;
		// +" - "+methodName;
	}

	/**
	 * <p>
	 * Getter for the field <code>className</code>.
	 * </p>
	 * 
	 * @return a {@link java.lang.String} object.
	 */
	public String getClassName() {
		return className;
	}

	/**
	 * <p>
	 * Getter for the field <code>methodName</code>.
	 * </p>
	 * 
	 * @return a {@link java.lang.String} object.
	 */
	public String getMethodName() {
		return methodName;
	}

	/**
	 * <p>
	 * explain
	 * </p>
	 * 
	 * @return a {@link java.lang.String} object.
	 */
	public String explain() {
		StringBuilder r = new StringBuilder();
		r.append(getName() + ":\n");

		int i = 0;
		for (BytecodeInstruction instruction : instructions) {
			i++;
			r.append("\t" + i + ")\t" + instruction.toString() + "\n");
		}

		return r.toString();
	}

	// inherited from Object

	/** {@inheritDoc} */
	@Override
	public String toString() {

		String r = "BB" + id;

		if (instructions.size() < 5)
			for (BytecodeInstruction ins : instructions)
				r = r.trim() + " " + ins.getInstructionType();
		else
			r += " " + getFirstInstruction().getInstructionType() + " ... "
			        + getLastInstruction().getInstructionType();

		int startLine = getFirstLine();
		int endLine = getLastLine();
		r += " l" + (startLine == -1 ? "?" : startLine + "");
		r += "-l" + (endLine == -1 ? "?" : endLine + "");

		return r;
	}



	// sanity check

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result
				+ ((className == null) ? 0 : className.hashCode());
		result = prime * result + id;
		result = prime * result
				+ ((instructions == null) ? 0 : instructions.hashCode());
		result = prime * result + (isAuxiliaryBlock ? 1231 : 1237);
		result = prime * result
				+ ((methodName == null) ? 0 : methodName.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (!(obj instanceof BasicBlock))
			return false;
		BasicBlock other = (BasicBlock) obj;
		if (id != other.id)
			return false;
		if (className == null) {
			if (other.className != null)
				return false;
		} else if (!className.equals(other.className))
			return false;
		if (methodName == null) {
			if (other.methodName != null)
				return false;
		} else if (!methodName.equals(other.methodName))
			return false;
		if (instructions == null) {
			if (other.instructions != null)
				return false;
		} else if (!instructions.equals(other.instructions))
			return false;
		if (isEntryBlock() != other.isEntryBlock())
			return false;
		if (isExitBlock() != other.isExitBlock())
			return false;
		return true;
	}

	/**
	 * <p>
	 * checkSanity
	 * </p>
	 */
	public void checkSanity() {

		logger.debug("checking sanity of " + toString());

		// TODO

		// not true, there are branches that don't really jump
		// for example if you have no statement in a then-part:
		// if (exp) { ; }
		// you will not have a second outgoing edge for that if

		// for(BytecodeInstruction instruction : instructions) {
		// if (!instruction.equals(getLastInstruction())
		// && instruction.isActualBranch())
		// throw new IllegalStateException(
		// "expect actual branches to always end a basic block: "+instruction.toString()+" \n"+explain());
		// }

		// TODO handle specialBlocks
	}

	/**
	 * <p>
	 * isEntryBlock
	 * </p>
	 * 
	 * @return a boolean.
	 */
	public boolean isEntryBlock() {
		return false;
	}

	/**
	 * <p>
	 * isExitBlock
	 * </p>
	 * 
	 * @return a boolean.
	 */
	public boolean isExitBlock() {
		return false;
	}

	/* (non-Javadoc)
	 * @see java.lang.Iterable#iterator()
	 */
	/** {@inheritDoc} */
	@Override
	public Iterator<BytecodeInstruction> iterator() {
		return instructions.iterator();
	}
}
